/*
 * using_type_traits_to_restrain_template_type_parameter.cpp
 *
 * Demonstrating how to use type traits to restrain template type parameter.
 *
 */

#include <iostream>
#include <string>
#include <type_traits>


using std::string;


class Control
{
  public:
    virtual int hands() = 0;
};


class One_Handed : public Control
{
  public:
    int hands() { return 1; }
};


class Two_Handed : public Control
{
  public:
    int hands() { return 2; }
};


class Bow : public Two_Handed
{
  public:
    virtual string name() = 0;
};


class Recurve_Bow : public Bow
{
  public:
    string name() { return "Recurve Bow"; }
};


class Compound_Bow : public Bow
{
  public:
    string name() { return "Compound Bow"; }
};


class Gun
{
  public:
    virtual string name() = 0;
};


class Pistol : public Gun, public One_Handed
{
  public:
    string name() { return "Pistol"; }
};


class Rifle : public Gun, public Two_Handed
{
  public:
    string name() { return "Rifle"; }
};


template
<
    class _B,
    class = typename std::enable_if<std::is_base_of<Bow, _B>::value>::type
>
class Archery_Game
{
  private:
    _B __b__;
  public:
    void begin() { std::cout << __b__.name() << " Archery Game begins..."; }
    void end()   { std::cout << "Game end."    << std::endl; }
};


template
<
    class _G,
    class _C,
    typename std::enable_if<std::is_base_of<Gun, _G>::value, int>::type = 0,
    typename std::enable_if<std::is_base_of<Control, _C>::value, bool>::type = false,
    class = typename std::enable_if<std::is_same<_G, typename std::conditional<std::is_same<_C, One_Handed>::value, Pistol, Rifle>::type>::value>::type
>
class Shooting_Game
{
  private:
    _G __g__;
    _C __c__;
  public:
    void begin() { std::cout << __g__.name() << " / " << __c__.hands() << " hand(s). Shooting Game begins..."; }
    void end()   { std::cout << "Game end." << std::endl; }
};


int main()
{
    Archery_Game<Recurve_Bow> ag;
    //Archery_Game<Gun> ag;
    ag.begin();
    ag.end();

    Shooting_Game<Pistol, One_Handed> sg1;
    //Shooting_Game<Pistol, Two_Handed> sg1;
    sg1.begin();
    sg1.end();

    Shooting_Game<Rifle, Two_Handed> sg2;
    //Shooting_Game<Rifle, One_Handed> sg2;
    sg2.begin();
    sg2.end();

    return 0;
}
